﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Cache;
using System.Text;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Xml.Linq;

using Perseus;
using Perseus.Data;
using Perseus.Plugins;
using Perseus.Xml;

using Hetymine.Executioner.Commands;
using Hetymine.Executioner.Data;
using Hetymine.Executioner.Plugins;

/** TODO
 * 
 * - Image manager, automatically log images, show in nice flow panel each image with options to remove from logs or delete from imgur
 * - Update to use imgur api v2
 */

namespace Hetymine.Executioner.Plugins.General.Imgur {
    public class Imgur : Plugin, IExecutable, ILoader, IConfigurable<IniFile> {
        private ImgurConfigurator _Configurator;

        public Imgur() {
            this.PluginInfo = PluginInfo.FromAssembly(
                "Imgur",
                this.GetType().Assembly,
                "Blake Hancock",
                "http://hetymine.com/executioner/plugins/general/imgur/"
            );
            
            this.CallStrings = new List<string>();
            this.CallStrings.Add("imgur");
        }

        #region IExecutable
        public List<string> CallStrings { get; set; }

        public ExecutionResult Execute(string parameters) {
            // Open log file
            if (parameters == "log") {
                return Executioner.ExecuteCommand(
                    Executioner.PluginDirectory +
                    "Imgur" + Path.DirectorySeparatorChar +
                    "log.txt"
                ).ToExecutionResult();
            }
            // Clear log
            else if (parameters == "clear") {
                string logFile = Executioner.PluginDirectory +
                    "Imgur" + Path.DirectorySeparatorChar +
                    "log.txt";

                if (File.Exists(logFile)) {
                    File.Delete(logFile);
                }

                return ExecutionResult.Success;
            }

            string fileName = this.ImageFileName(parameters);
            if (fileName.IsEmpty()) {
                App.Log("Imgur: Filename not specified.");
                return ExecutionResult.Failure;
            }

            /* Imgur uploading code based on ZScreen
             * http://code.google.com/p/zscreen/
             */
            string response;
            using (FileStream s = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) {
                response = this.UploadImage(s, Path.GetFileName(fileName));
            }

            if (response.IsEmpty()) {
                App.Log("Imgur: No response from imgur.com.");
                return ExecutionResult.Error;
            }

            XDocument xmlDoc = XDocument.Parse(response);
            XElement element = xmlDoc.Element("rsp");

            if (element != null) {
                switch (element.AttributeFirstValue("status", "stat")) {
                    case "ok":
                        string[] pages = new string[5];
                        pages[0] = element.Element("imgur_page").ValueOrEmpty();
                        pages[1] = element.Element("original_image").ValueOrEmpty();
                        pages[2] = element.Element("large_thumbnail").ValueOrEmpty();
                        pages[3] = element.Element("small_thumbnail").ValueOrEmpty();
                        pages[4] = element.Element("delete_page").ValueOrEmpty();

                        int logPages = Executioner.Config.GetInt("Imgur", "Log Pages", 0);
                        if (logPages > 0) {
                            string directory = Executioner.Directory +
                                "plugins" + Path.DirectorySeparatorChar +
                                "Imgur" + Path.DirectorySeparatorChar;

                            if (fileName.StartsWith(directory)) {
                                fileName = "Clipboard Image";
                            }

                            string log = "Source: " + fileName + Environment.NewLine;

                            if ((logPages | 1) == logPages) {
                                log += "Imgur Page: " + pages[0] + Environment.NewLine;
                            }
                            if ((logPages | 2) == logPages) {
                                log += "Origional Image: " + pages[1] + Environment.NewLine;
                            }
                            if ((logPages | 4) == logPages) {
                                log += "Large Thumbnail: " + pages[2] + Environment.NewLine;
                            }
                            if ((logPages | 8) == logPages) {
                                log += "Small Thumbnail: " + pages[3] + Environment.NewLine;
                            }
                            if ((logPages | 16) == logPages) {
                                log += "Delete Page: " + pages[4] + Environment.NewLine;
                            }

                            log += Environment.NewLine;

                            string logFile = directory + "log.txt";
                            File.AppendAllText(logFile, log, Encoding.UTF8);
                        }

                        if (Executioner.Config.GetBool("Imgur", "Copy To Clipboard", true)) {
                            int page = Executioner.Config.GetInt("Imgur", "Copy To Clipboard Page", 0);
                            Clipboard.SetText(pages[page]);
                        }

                        if (Executioner.Config.GetBool("Imgur", "Open Url In Browser", true)) {
                            int page = Executioner.Config.GetInt("Imgur", "Open Url In Browser Page", 0);
                            Executioner.ExecuteCommand(pages[page]);
                        }
                        break;
                    case "fail":
                    default:
                        // TODO: Log status code and message
                        App.Log("Imgur: Failed to upload image.");
                        return ExecutionResult.Error;
                }
            }

            return ExecutionResult.Success;
        }

        private string ImageFileName(string parameters) {
            if (parameters.IsEmpty()) {
                if (Clipboard.ContainsImage()) {
                    string fileName = Executioner.Directory +
                        "plugins" + Path.DirectorySeparatorChar +
                        "Imgur" + Path.DirectorySeparatorChar +
                        "temp" + Path.DirectorySeparatorChar;

                    try {
                        using (FileStream fs = new FileStream(fileName + "temp_bmp.bmp", FileMode.Create, FileAccess.Write)) {
                            BitmapSource image = Clipboard.GetImage();
                            BmpBitmapEncoder bbe = new BmpBitmapEncoder();
                            bbe.Frames.Add(BitmapFrame.Create(image));
                            bbe.Save(fs);
                        }

                        Uri uri = new Uri(fileName + "temp_bmp.bmp");
                        fileName += "temp.png";
                        using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) {
                            PngBitmapEncoder pbe = new PngBitmapEncoder();
                            pbe.Interlace = PngInterlaceOption.Off;
                            pbe.Frames.Add(BitmapFrame.Create(uri));
                            pbe.Save(fs);
                        }

                        return fileName;
                    }
                    catch {
                        App.Log("Imgur: Error caching image from clipboard.");
                        Executioner.Sounds.Error();
                        return string.Empty;
                    }
                }
                else if (Clipboard.ContainsText()) {
                    string fileName = this.EnsureRootedPath(Clipboard.GetText());

                    if (File.Exists(fileName)) {
                        return fileName;
                    }
                }
            }
            else {
                string fileName = this.EnsureRootedPath(parameters);

                if (File.Exists(fileName)) {
                    return fileName;
                }
            }

            return null;
        }
        private string EnsureRootedPath(string fileName) {
            if (Path.IsPathRooted(fileName)) {
                return fileName;
            }

            string newFileName = Executioner.Config.GetString(
                "Imgur",
                "Default Image Directory",
                Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)
            );

            if (!newFileName.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) {
                newFileName += Path.DirectorySeparatorChar;
            }

            return newFileName + fileName;
        }

        private string UploadImage(Stream data, string fileName) {
            string url = "http://imgur.com/api/upload.xml";

            Dictionary<string, string> arguments = new Dictionary<string, string>();
            arguments.Add("key", "080e44c439ae0b99d6c5265b03592910");


            string boundary = "---------------" + DateTime.Now.Ticks.ToString("x");

            using (MemoryStream stream = new MemoryStream()) {
                byte[] bytes = this.MakeInputContent(boundary, arguments);
                stream.Write(bytes, 0, bytes.Length);

                string contentType = PIO.GetMimeType(fileName);
                bytes = this.MakeFileInputContent(boundary, fileName, data, contentType);
                stream.Write(bytes, 0, bytes.Length);

                bytes = Encoding.UTF8.GetBytes(string.Format("--{0}--\r\n", boundary));
                stream.Write(bytes, 0, bytes.Length);

                using (HttpWebResponse response = this.GetResponseUsingPost(url, stream, boundary)) {
                    return this.ResponseToString(response);
                }
            }
        }
        private byte[] MakeInputContent(string boundary, Dictionary<string, string> contents) {
            using (MemoryStream stream = new MemoryStream()) {
                byte[] bytes;

                if (contents != null) {
                    foreach (KeyValuePair<string, string> content in contents) {
                        bytes = this.MakeInputContent(boundary, content.Key, content.Value);
                        stream.Write(bytes, 0, bytes.Length);
                    }
                }

                return stream.ToArray();
            }
        }
        private byte[] MakeInputContent(string boundary, string name, string value) {
            string format = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n", boundary, name, value);
            return Encoding.UTF8.GetBytes(format);
        }
        private byte[] MakeFileInputContent(string boundary, string fileName, Stream content, string contentType) {
            string format = string.Format(
                "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n",
                boundary, "image", fileName, contentType
            );

            using (MemoryStream stream = new MemoryStream()) {
                byte[] buffer;

                buffer = Encoding.UTF8.GetBytes(format);
                stream.Write(buffer, 0, buffer.Length);

                content.CopyStream(stream);

                buffer = Encoding.UTF8.GetBytes("\r\n");
                stream.Write(buffer, 0, buffer.Length);

                return stream.ToArray();
            }
        }
        private HttpWebResponse GetResponseUsingPost(string url, Stream stream, string boundary) {
            try {
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                //request.AllowWriteStreamBuffering = ProxySettings.ProxyConfig != ProxyConfigType.NoProxy;
                request.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore);
                request.ContentLength = stream.Length;
                request.ContentType = "multipart/form-data; boundary=" + boundary;
                request.KeepAlive = false;
                request.Method = "POST";
                request.ProtocolVersion = HttpVersion.Version11;
                //request.Proxy = ProxySettings.GetWebProxy;
                request.ServicePoint.Expect100Continue = false;
                request.ServicePoint.UseNagleAlgorithm = false;
                request.Timeout = -1;
                request.UserAgent = Executioner.Config.GetString("Executioner", "User Agent", Executioner.VersionString);             

                using (Stream requestStream = request.GetRequestStream()) {
                    stream.Position = 0;
                    byte[] buffer = new byte[4096];
                    int bytesRead;

                    while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0) {
                        requestStream.Write(buffer, 0, bytesRead);
                    }
                }

                return (HttpWebResponse)request.GetResponse();
            }
            catch { }

            return null;
        }
        private string ResponseToString(WebResponse response) {
            if (response != null) {
                using (response)
                using (StreamReader reader = new StreamReader(response.GetResponseStream())) {
                    return reader.ReadToEnd();
                }
            }

            return null;
        }
        #endregion

        #region ILoader
        public void Load() {
            string directory = Executioner.Directory +
                "plugins" + Path.DirectorySeparatorChar +
                "Imgur" + Path.DirectorySeparatorChar +
                "temp" + Path.DirectorySeparatorChar;
            if (!Directory.Exists(directory)) {
                Directory.CreateDirectory(directory);
            }
        }
        public void Unload() {
            // Clean up temp data.
            string directory = Executioner.Directory +
                "plugins" + Path.DirectorySeparatorChar +
                "Imgur" + Path.DirectorySeparatorChar;

            string[] files = Directory.GetFiles(directory + "temp" + Path.DirectorySeparatorChar);
            foreach (string s in files) {
                File.Delete(s);
            }
            Directory.Delete(directory + "temp" + Path.DirectorySeparatorChar);

            // If no log file then delete imgur directory
            if (Directory.GetFiles(directory).Length == 0) {
                Directory.Delete(directory);
            }
        }
        #endregion
        #region IConfigurable<IniFile>
        public IConfigurator<IniFile> Configurator {
            get { 
                if (this._Configurator == null) {
                    this._Configurator = new ImgurConfigurator();
                }

                return this._Configurator;
            }
        }
        #endregion
    }
}
